/* CSci4061 F2012 Assignment 5
 * section: 3
 * date: 12/11/12
 * name: Jeff Goin, Jack Jones
 * id: 2999885, 4220764 */

#ifndef _REENTRANT
#define _REENTRANT
#endif

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>


static int master_fd = -1;
pthread_mutex_t accept_con_mutex = PTHREAD_MUTEX_INITIALIZER;

int makeargv(const char *s, const char *delimiters, char ***argvp) {
    int error;
    int i;
    int numtokens;
    const char *snew;
    char *t;

    if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) {
        errno = EINVAL;
	return -1;
    }
    *argvp = NULL;
    snew = s + strspn(s, delimiters);
    if ((t = malloc(strlen(snew) + 1)) == NULL)
        return -1;
    strcpy(t,snew);
    numtokens = 0;
    if (strtok(t, delimiters) != NULL)
        for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;

    if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {
        error = errno;
	free(t);
	errno = error;
	return -1;
    }

    if (numtokens == 0)
        free(t);
    else {
        strcpy(t,snew);
	**argvp = strtok(t,delimiters);
	for (i=1; i<numtokens; i++)
	    *((*argvp) +i) = strtok(NULL,delimiters);
    }
    *((*argvp) + numtokens) = NULL;
    return numtokens;
}

void freemakeargv(char **argv) {
    if (argv == NULL)
        return;
    if (*argv != NULL)
        free(*argv);
    free(argv);
}


/**********************************************
 * init
   - port is the number of the port you want the server to be
     started on
   - initializes the connection acception/handling system
   - YOU MUST CALL THIS EXACTLY ONCE (not once per thread,
     but exactly one time, in the main thread of your program)
     BEFORE USING ANY OF THE FUNCTIONS BELOW
   - if init encounters any errors, it will call exit().
************************************************/

void init(int port) {

    master_fd = socket(AF_INET, SOCK_STREAM, 0);

    int enable = 1;
    setsockopt(master_fd, SOL_SOCKET, SO_REUSEADDR, (char*)&enable, sizeof(int));
  
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htons(INADDR_ANY);
    server.sin_port = htons(port);

    if (bind(master_fd, (struct sockaddr*) &server, sizeof(server)) < 0) {
        perror("Error binding!");
	exit(1);
    }

    if (listen(master_fd, 5) < 0) {
        perror("Error listening!");
	exit(1);
    }
}

/**********************************************
 * accept_connection - takes no parameters
   - returns a file descriptor for further request processing.
     DO NOT use the file descriptor on your own -- use
     get_request() instead.
   - if the return value is negative, the thread calling
     accept_connection must exit by calling pthread_exit().
***********************************************/

int accept_connection(void) {
    struct sockaddr_in client_addr;
    int size = sizeof(struct sockaddr);
    pthread_mutex_lock(&accept_con_mutex);
    int sock = accept(master_fd, (struct sockaddr*) &client_addr, &size);
    pthread_mutex_unlock(&accept_con_mutex);
    return sock;
}

/**********************************************
 * get_request
   - parameters:
      - fd is the file descriptor obtained by accept_connection()
        from where you wish to get a request
      - filename is the location of a character buffer in which
        this function should store the requested filename. (Buffer
        should be of size 1024 bytes.)
   - returns 0 on success, nonzero on failure. You must account
     for failures because some connections might send faulty
     requests. This is a recoverable error - you must not exit
     inside the thread that called get_request. After an error, you
     must NOT use a return_request or return_error function for that
     specific 'connection'.
************************************************/

int get_request(int fd, char *filename) {

    char buffer[1024];
    char** req;

    if (read(fd, buffer, 1024) < 0) {
        perror("Error receiving request from client");
	return -1;
    }

    if (makeargv(buffer, " ", &req) < 0) {
        perror("Parsing error!");
	return -1;
    }

    if (strcmp(req[0], "GET") != 0) {
        perror("Incorrect format, should be GET");
	return -1;
    }

    char filestr[256];
    strcpy(filestr, req[1]);

    int i;
    int j=1;
    for(i=0;i<strlen(filestr);i++){
        if(filestr[i]=='/' && filestr[j]=='/'){
	    printf("Error: path to file contains \"//\"");
	    return -1;
	}
	if(filestr[i] =='.' && filestr[j]=='.') {
	    printf("Error: path to file contains \"..\"");
	    return -1;
	}
	j++;
    }

    strncpy(filename, req[1], 1023);
    freemakeargv(req);
    return 0;
}

/**********************************************
 * return_result
   - returns the contents of a file to the requesting client
   - parameters:
      - fd is the file descriptor obtained by accept_connection()
        to where you wish to return the result of a request
      - content_type is a pointer to a string that indicates the
        type of content being returned. possible types include
        "text/html", "text/plain", "image/gif", "image/jpeg" cor-
        responding to .html, .txt, .gif, .jpg files.
      - buf is a pointer to a memory location where the requested
        file has been read into memory (the heap). return_result
        will use this memory location to return the result to the
        user. (remember to use -D_REENTRANT for CFLAGS.) you may
        safely deallocate the memory after the call to
        return_result (if it will not be cached).
      - numbytes is the number of bytes the file takes up in buf
   - returns 0 on success, nonzero on failure.
************************************************/
int return_result(int fd, char *content_type, char *buf, int numbytes) {
  // send headers back to the socketfd, connection: close, content-type, content-length, etc
  // then finally send back the resulting file
  // then close the connection
  
    FILE *stream = fdopen(fd, "w");

    fprintf(stream, "HTTP/1.1 200 OK\nContent-Type: %s\nContent-Length: %d\nConnection: Close\n\n", content_type, numbytes);

    if (write(fd, buf, numbytes) < 0) {
      perror("Failed to write");
      return -1;
    }

    fclose(stream);
    
    return 0;
}

/**********************************************
 * return_error
   - returns an error message in response to a bad request
   - parameters:
      - fd is the file descriptor obtained by accept_connection()
        to where you wish to return the error
      - buf is a pointer to the location of the error text
   - returns 0 on success, nonzero on failure.
************************************************/
int return_error(int fd, char *buf) {
  // send 404 headers back to the socketfd, connection: close, content-type: text/plain, content-length
  // send back the error message as a piece of text.
  // then close the connection

    FILE *stream = fdopen(fd,"w");

    fprintf(stream, "HTTP/1.0 404 Not Found\nContent-Length: %d\nConnection: Close\n\n");

    if (write(fd, buf, strlen(buf)) < 0) {
      perror("Failed to write");
      return -1;
    }

    fclose(stream);
    /* A 404 error might look like this string, where %d is the content length:
     * "HTTP/1.0 404 Not Found\nContent-Length: %d\nConnection: Close\n\n"
     */

    return 0;

}
